// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/test/test_suite.h"

#include <memory>

#include "base/at_exit.h"
#include "base/base_paths.h"
#include "base/base_switches.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/debug/debugger.h"
#include "base/debug/stack_trace.h"
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/i18n/icu_util.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/path_service.h"
#include "base/process/launch.h"
#include "base/process/memory.h"
#include "base/test/gtest_xml_unittest_result_printer.h"
#include "base/test/gtest_xml_util.h"
#include "base/test/launcher/unit_test_launcher.h"
#include "base/test/multiprocess_test.h"
#include "base/test/test_switches.h"
#include "base/test/test_timeouts.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/multiprocess_func_list.h"

#if defined(OS_MACOSX)
#include "base/mac/scoped_nsautorelease_pool.h"
#if defined(OS_IOS)
#include "base/test/test_listener_ios.h"
#endif // OS_IOS
#endif // OS_MACOSX

#if !defined(OS_WIN)
#include "base/i18n/rtl.h"
#if !defined(OS_IOS)
#include "base/strings/string_util.h"
#include "third_party/icu/source/common/unicode/uloc.h"
#endif
#endif

#if defined(OS_ANDROID)
#include "base/test/test_support_android.h"
#endif

#if defined(OS_IOS)
#include "base/test/test_support_ios.h"
#endif

namespace base {

namespace {

    class MaybeTestDisabler : public testing::EmptyTestEventListener {
    public:
        void OnTestStart(const testing::TestInfo& test_info) override
        {
            ASSERT_FALSE(TestSuite::IsMarkedMaybe(test_info))
                << "Probably the OS #ifdefs don't include all of the necessary "
                   "platforms.\nPlease ensure that no tests have the MAYBE_ prefix "
                   "after the code is preprocessed.";
        }
    };

    class TestClientInitializer : public testing::EmptyTestEventListener {
    public:
        TestClientInitializer()
            : old_command_line_(CommandLine::NO_PROGRAM)
        {
        }

        void OnTestStart(const testing::TestInfo& test_info) override
        {
            old_command_line_ = *CommandLine::ForCurrentProcess();
        }

        void OnTestEnd(const testing::TestInfo& test_info) override
        {
            *CommandLine::ForCurrentProcess() = old_command_line_;
        }

    private:
        CommandLine old_command_line_;

        DISALLOW_COPY_AND_ASSIGN(TestClientInitializer);
    };

} // namespace

int RunUnitTestsUsingBaseTestSuite(int argc, char** argv)
{
    TestSuite test_suite(argc, argv);
    return LaunchUnitTests(argc, argv,
        Bind(&TestSuite::Run, Unretained(&test_suite)));
}

TestSuite::TestSuite(int argc, char** argv)
    : initialized_command_line_(false)
    , created_feature_list_(false)
{
    PreInitialize();
    InitializeFromCommandLine(argc, argv);
}

#if defined(OS_WIN)
TestSuite::TestSuite(int argc, wchar_t** argv)
    : initialized_command_line_(false)
    , created_feature_list_(false)
{
    PreInitialize();
    InitializeFromCommandLine(argc, argv);
}
#endif // defined(OS_WIN)

TestSuite::~TestSuite()
{
    if (initialized_command_line_)
        CommandLine::Reset();
}

void TestSuite::InitializeFromCommandLine(int argc, char** argv)
{
    initialized_command_line_ = CommandLine::Init(argc, argv);
    testing::InitGoogleTest(&argc, argv);
    testing::InitGoogleMock(&argc, argv);

#if defined(OS_IOS)
    InitIOSRunHook(this, argc, argv);
#endif
}

#if defined(OS_WIN)
void TestSuite::InitializeFromCommandLine(int argc, wchar_t** argv)
{
    // Windows CommandLine::Init ignores argv anyway.
    initialized_command_line_ = CommandLine::Init(argc, NULL);
    testing::InitGoogleTest(&argc, argv);
    testing::InitGoogleMock(&argc, argv);
}
#endif // defined(OS_WIN)

void TestSuite::PreInitialize()
{
#if defined(OS_WIN)
    testing::GTEST_FLAG(catch_exceptions) = false;
#endif
    EnableTerminationOnHeapCorruption();
#if defined(OS_LINUX) && defined(USE_AURA)
    // When calling native char conversion functions (e.g wrctomb) we need to
    // have the locale set. In the absence of such a call the "C" locale is the
    // default. In the gtk code (below) gtk_init() implicitly sets a locale.
    setlocale(LC_ALL, "");
#endif // defined(OS_LINUX) && defined(USE_AURA)

    // On Android, AtExitManager is created in
    // testing/android/native_test_wrapper.cc before main() is called.
#if !defined(OS_ANDROID)
    at_exit_manager_.reset(new AtExitManager);
#endif

    // Don't add additional code to this function.  Instead add it to
    // Initialize().  See bug 6436.
}

// static
bool TestSuite::IsMarkedMaybe(const testing::TestInfo& test)
{
    return strncmp(test.name(), "MAYBE_", 6) == 0;
}

void TestSuite::CatchMaybeTests()
{
    testing::TestEventListeners& listeners = testing::UnitTest::GetInstance()->listeners();
    listeners.Append(new MaybeTestDisabler);
}

void TestSuite::ResetCommandLine()
{
    testing::TestEventListeners& listeners = testing::UnitTest::GetInstance()->listeners();
    listeners.Append(new TestClientInitializer);
}

void TestSuite::AddTestLauncherResultPrinter()
{
    // Only add the custom printer if requested.
    if (!CommandLine::ForCurrentProcess()->HasSwitch(
            switches::kTestLauncherOutput)) {
        return;
    }

    FilePath output_path(CommandLine::ForCurrentProcess()->GetSwitchValuePath(
        switches::kTestLauncherOutput));

    // Do not add the result printer if output path already exists. It's an
    // indicator there is a process printing to that file, and we're likely
    // its child. Do not clobber the results in that case.
    if (PathExists(output_path)) {
        LOG(WARNING) << "Test launcher output path " << output_path.AsUTF8Unsafe()
                     << " exists. Not adding test launcher result printer.";
        return;
    }

    XmlUnitTestResultPrinter* printer = new XmlUnitTestResultPrinter;
    CHECK(printer->Initialize(output_path));
    testing::TestEventListeners& listeners = testing::UnitTest::GetInstance()->listeners();
    listeners.Append(printer);
}

// Don't add additional code to this method.  Instead add it to
// Initialize().  See bug 6436.
int TestSuite::Run()
{
#if defined(OS_IOS)
    RunTestsFromIOSApp();
#endif

#if defined(OS_MACOSX)
    mac::ScopedNSAutoreleasePool scoped_pool;
#endif

    Initialize();
    std::string client_func = CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
        switches::kTestChildProcess);

    // Check to see if we are being run as a client process.
    if (!client_func.empty())
        return multi_process_function_list::InvokeChildProcessTest(client_func);
#if defined(OS_IOS)
    test_listener_ios::RegisterTestEndListener();
#endif

    int result = RUN_ALL_TESTS();

#if defined(OS_MACOSX)
    // This MUST happen before Shutdown() since Shutdown() tears down
    // objects (such as NotificationService::current()) that Cocoa
    // objects use to remove themselves as observers.
    scoped_pool.Recycle();
#endif

    Shutdown();

    return result;
}

// static
void TestSuite::UnitTestAssertHandler(const std::string& str)
{
#if defined(OS_ANDROID)
    // Correlating test stdio with logcat can be difficult, so we emit this
    // helpful little hint about what was running.  Only do this for Android
    // because other platforms don't separate out the relevant logs in the same
    // way.
    const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info();
    if (test_info) {
        LOG(ERROR) << "Currently running: " << test_info->test_case_name() << "."
                   << test_info->name();
        fflush(stderr);
    }
#endif // defined(OS_ANDROID)

    // The logging system actually prints the message before calling the assert
    // handler. Just exit now to avoid printing too many stack traces.
    _exit(1);
}

void TestSuite::SuppressErrorDialogs()
{
#if defined(OS_WIN)
    UINT new_flags = SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX;

    // Preserve existing error mode, as discussed at
    // http://blogs.msdn.com/oldnewthing/archive/2004/07/27/198410.aspx
    UINT existing_flags = SetErrorMode(new_flags);
    SetErrorMode(existing_flags | new_flags);

#if defined(_DEBUG) && defined(_HAS_EXCEPTIONS) && (_HAS_EXCEPTIONS == 1)
    // Suppress the "Debug Assertion Failed" dialog.
    // TODO(hbono): remove this code when gtest has it.
    // http://groups.google.com/d/topic/googletestframework/OjuwNlXy5ac/discussion
    _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
    _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR);
#endif // defined(_DEBUG) && defined(_HAS_EXCEPTIONS) && (_HAS_EXCEPTIONS == 1)
#endif // defined(OS_WIN)
}

void TestSuite::Initialize()
{
#if !defined(OS_IOS)
    if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kWaitForDebugger)) {
        debug::WaitForDebugger(60, true);
    }
#endif

    // Set up a FeatureList instance, so that code using that API will not hit a
    // an error that it's not set. If a FeatureList was created in this way (i.e.
    // one didn't exist previously), it will be cleared in Shutdown() via
    // ClearInstanceForTesting().
    created_feature_list_ = FeatureList::InitializeInstance(std::string(), std::string());

#if defined(OS_IOS)
    InitIOSTestMessageLoop();
#endif // OS_IOS

#if defined(OS_ANDROID)
    InitAndroidTest();
#else
    // Initialize logging.
    FilePath exe;
    PathService::Get(FILE_EXE, &exe);
    FilePath log_filename = exe.ReplaceExtension(FILE_PATH_LITERAL("log"));
    logging::LoggingSettings settings;
    settings.logging_dest = logging::LOG_TO_ALL;
    settings.log_file = log_filename.value().c_str();
    settings.delete_old = logging::DELETE_OLD_LOG_FILE;
    logging::InitLogging(settings);
    // We want process and thread IDs because we may have multiple processes.
    // Note: temporarily enabled timestamps in an effort to catch bug 6361.
    logging::SetLogItems(true, true, true, true);
#endif // else defined(OS_ANDROID)

    CHECK(debug::EnableInProcessStackDumping());
#if defined(OS_WIN)
    RouteStdioToConsole(true);
    // Make sure we run with high resolution timer to minimize differences
    // between production code and test code.
    Time::EnableHighResolutionTimer(true);
#endif // defined(OS_WIN)

    // In some cases, we do not want to see standard error dialogs.
    if (!debug::BeingDebugged() && !CommandLine::ForCurrentProcess()->HasSwitch("show-error-dialogs")) {
        SuppressErrorDialogs();
        debug::SetSuppressDebugUI(true);
        logging::SetLogAssertHandler(UnitTestAssertHandler);
    }

    if (!CommandLine::ForCurrentProcess()->HasSwitch(
            switches::kTestDoNotInitializeIcu)) {
        i18n::InitializeICU();
    }
    // On the Mac OS X command line, the default locale is *_POSIX. In Chromium,
    // the locale is set via an OS X locale API and is never *_POSIX.
    // Some tests (such as those involving word break iterator) will behave
    // differently and fail if we use *POSIX locale. Setting it to en_US here
    // does not affect tests that explicitly overrides the locale for testing.
    // This can be an issue on all platforms other than Windows.
    // TODO(jshin): Should we set the locale via an OS X locale API here?
#if !defined(OS_WIN)
#if defined(OS_IOS)
    i18n::SetICUDefaultLocale("en_US");
#else
    std::string default_locale(uloc_getDefault());
    if (EndsWith(default_locale, "POSIX", CompareCase::INSENSITIVE_ASCII))
        i18n::SetICUDefaultLocale("en_US");
#endif
#endif

    CatchMaybeTests();
    ResetCommandLine();
    AddTestLauncherResultPrinter();

    TestTimeouts::Initialize();

    trace_to_file_.BeginTracingFromCommandLineOptions();
}

void TestSuite::Shutdown()
{
    // Clear the FeatureList that was created by Initialize().
    if (created_feature_list_)
        FeatureList::ClearInstanceForTesting();
}

} // namespace base
